/*
Copyright (C) 2010 Andrew Dieffenbach (www.puzzud.com).

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

package com.puzzud.engine.video;

import com.puzzud.engine.video.VideoObject;
import com.puzzud.engine.video.VideoImage;
import com.puzzud.engine.video.VideoMoveableObject2D;
import com.puzzud.engine.video.VideoDisplay;
import com.puzzud.engine.utility.Script;
import com.puzzud.engine.utility.GameShell;

import java.awt.*;
//import java.awt.Image;
import java.awt.image.*;
import java.awt.geom.AffineTransform;
//import java.awt.MediaTracker;

import java.util.Vector;

// VideoSprite
public class VideoSprite extends VideoMoveableObject2D
{
	protected Vector<VideoImage> videoImageList;
	protected int attributes;
	protected int state;
	protected Script animation;
	
	private Color color;
	private float[] colorWeight;
	protected float alpha;

        protected float angle;
        protected Point rotationAxis;
	
	protected int realWidth;
	protected int realHeight;
	
	GameShell gameShell;
	
	public static final int EXTRACT_MODE_CELL			= 0;// Mode to extract a cell from a bitmap for sprite
	public static final int EXTRACT_MODE_ABS			= 1;// Mode to extract the bitmap for a sprite
	
	public static final int SPRITE_STATE_DEAD			= 0;// Sprite is dead
	public static final int SPRITE_STATE_ALIVE			= 1;// Sprite is alive
	public static final int SPRITE_STATE_DYING			= 2;// Sprite is dying
	public static final int SPRITE_STATE_ANIM_DONE			= 4;// Sprite is done animating
	public static final int SPRITE_STATE_ANIM_RUNNING		= 8;// Sprite is currently animating
	public static final int SPRITE_STATE_ANIM_RESET			= 16;// Sprite is has reset animation
	
	public static final int SPRITE_ATTR_SINGLE_FRAME		= 1;// Sprite has single frame
	public static final int SPRITE_ATTR_MULTI_FRAME			= 2;// Sprite has multiple frames
	public static final int SPRITE_ATTR_MULTI_ANIM			= 4;// Sprite has multiple animations
	public static final int SPRITE_ATTR_ANIM_ONE_SHOT		= 8;// Sprite will perform the animation once
	public static final int SPRITE_ATTR_VISIBLE			= 16;// Sprite is visible
	public static final int SPRITE_ATTR_BOUNCE			= 32;// Sprite bounces off edges
	public static final int SPRITE_ATTR_WRAPAROUND			= 64;// Sprite wraps around edges
	public static final int SPRITE_ATTR_LOADED			= 128;// Sprite has been loaded
	public static final int SPRITE_ATTR_CLONE			= 256;// Sprite is a clone
	
	public VideoSprite( int width, int height, int attributes, GameShell gs )
	{
		super();
		
		gameShell = gs;
		
		videoImageList = new Vector<VideoImage>();
		animation = new Script();
		
		setRealDimensions( width, height );
		setDimensions( width, height );
		this.attributes = attributes;
		
		// Set default state and specified attributes of sprite
		state		= SPRITE_STATE_ALIVE;
		this.attributes	= attributes;
	
		// Set attributes of the animation
		if( ( attributes & SPRITE_ATTR_SINGLE_FRAME ) > 0 )
		{
			animation.setAttributes( Script.SCRIPT_ATTR_SINGLE_FRAME );
		}
		else
		if( ( attributes & SPRITE_ATTR_MULTI_FRAME ) > 0 )
		{
			animation.setAttributes( Script.SCRIPT_ATTR_MULTI_FRAME );
		}
		else
		if( ( attributes & SPRITE_ATTR_MULTI_ANIM ) > 0 )
		{
			if( ( attributes & SPRITE_ATTR_ANIM_ONE_SHOT ) > 0 )
			{
				animation.setAttributes( Script.SCRIPT_ATTR_MULTI_SEQUENCE & Script.SCRIPT_ATTR_ONE_SHOT );
			}
			else
			{
				animation.setAttributes( Script.SCRIPT_ATTR_MULTI_SEQUENCE );
			}
		}
		
		animation.setNumberOfFrames( 0 );
		
		// Set default color and alpha levels;
		colorWeight = new float[] {1.0f, 1.0f, 1.0f, 1.0f};// This must be called before any color/alpha changes.
		setColor( colorWeight );
		setAlpha( 1.0f );

                rotationAxis = new Point();
                setRotationAxis( width / 2, height / 2 );   // Default center rotation axis.
	}
	
	protected void setRealDimensions( int width, int height )
	{
		realWidth = width;
		realHeight = height;
	}
	
	int getRealWidth()
	{
		// TODO Do this better.
		return( realWidth );
	}
	
	int getRealHeight()
	{
		// TODO Do this better.
		return( realHeight );
	}
	/*
	public void draw( VideoObject videoObject )
	{
		
	}
        */

        public void drawOnTo( VideoImage videoImage )
        {
            Graphics2D g2d = (Graphics2D) videoImage.getBuffer().getGraphics();
            Image tempImage = getBuffer();
            BufferedImage sourceImage;

            sourceImage = new BufferedImage( getRealWidth(),
                                             getRealHeight(),
                                             BufferedImage.TYPE_INT_ARGB );

            sourceImage.createGraphics().drawImage( tempImage, null, gameShell );

                final float[] offset = new float[] { 0.0f, 0.0f, 0.0f, 0.0f };
                RescaleOp rescaleOp = new RescaleOp( colorWeight, offset, null );

                if( isScaled() || ( angle != 0.0f ) )
                {
                        final AffineTransform identity = new AffineTransform();
                        AffineTransform at = new AffineTransform();

                        //at.setTransform( identity );

                        // Determine if the scale should be smoothen or exact.
                        if( getScaleType() == VideoObject2D.SCALE_TYPE_EXACT )
                        {
                            g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION,
                                                 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR );

                        }
                        else
                        {
                             g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION,
                                                   RenderingHints.VALUE_INTERPOLATION_BILINEAR  );
                        }

                        g2d.setTransform( at );

                        if( ( getXScale() == 0 ) && ( getYScale() == 0 ) )
                        {
                                g2d.scale( 0, 0 );
                        }
                        else
                        {
                                g2d.translate( getXPosition(), getYPosition() );
                                g2d.scale( getXScale(), getYScale() );

                                if( angle != 0.0f )
                                {
                                    g2d.rotate( angle, this.rotationAxis.x, this.rotationAxis.y );
                                }
                        }

                        g2d.drawImage( sourceImage, rescaleOp, 0, 0 );

                        g2d.setTransform( identity );
                }
                else
                {
                    g2d.drawImage( sourceImage, rescaleOp,
                                   getXPosition(), getYPosition() );
                }

        }
	
	public void drawOnTo( VideoDisplay videoDisplay )
	{
		Graphics2D g2d = videoDisplay.getBuffer();
		Image tempImage = getBuffer();
		BufferedImage sourceImage;
		
		sourceImage = new BufferedImage( getRealWidth(),
						 getRealHeight(),
						 BufferedImage.TYPE_INT_ARGB );
		
		sourceImage.createGraphics().drawImage( tempImage, null, gameShell );

                final float[] offset = new float[] { 0.0f, 0.0f, 0.0f, 0.0f };
                RescaleOp rescaleOp = new RescaleOp( colorWeight, offset, null );

                if( isScaled() || ( angle != 0.0f ) )
                {
                        final AffineTransform identity = new AffineTransform();
                        AffineTransform at = new AffineTransform();

                        //at.setTransform( identity );

                        // Determine if the scale should be smoothen or exact.
                        if( getScaleType() == VideoObject2D.SCALE_TYPE_EXACT )
                        {
                            g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION,
                                                 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR );

                        }
                        else
                        {
                             g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION,
                                                   RenderingHints.VALUE_INTERPOLATION_BILINEAR  );
                        }

                        g2d.setTransform( at );

                        if( ( getXScale() == 0 ) && ( getYScale() == 0 ) )
                        {
                                g2d.scale( 0, 0 );
                        }
                        else
                        {
                                g2d.translate( getXPosition(), getYPosition() );
                                g2d.scale( getXScale(), getYScale() );

                                if( angle != 0.0f )
                                {
                                    g2d.rotate( angle, this.rotationAxis.x, this.rotationAxis.y );
                                }
                        }

                        g2d.drawImage( sourceImage, rescaleOp, 0, 0 );

                        g2d.setTransform( identity );
                }
                else
                {
                    g2d.drawImage( sourceImage, rescaleOp,
                                   getXPosition(), getYPosition() );
                }
		
		//backBufferGraphics.drawImage( tempVideoSprite.getBuffer(),
		//			      tempVideoSprite.getXPosition(),
		//			      tempVideoSprite.getYPosition(), gameShell );
	}
	
	public void loadFrame( VideoImage sourceImage, int cellXPosition, int cellYPosition,
						       int mode )
	{
		// Test the mode of extraction, cell based or absolute
		if( mode == EXTRACT_MODE_CELL )
		{
			// Re-compute x and y
			cellXPosition = ( cellXPosition * ( getWidth()  + 1 ) ) + 1;
			cellYPosition = ( cellYPosition * ( getHeight() + 1 ) ) + 1;
		}
		
		ImageProducer sourceProducer = sourceImage.getBuffer().getSource();
		ImageFilter cropFilter = new CropImageFilter( cellXPosition, cellYPosition,
							      getWidth(), getHeight() );
		
		
		Image tempImage = gameShell.createImage( new FilteredImageSource( sourceProducer, cropFilter ) );
		
		// NOTE Perhaps if I move this media tracking access outside of this object to a single media
		// tracker (perhaps in VideoSystem), things will load more concurrently.
		MediaTracker media_tracker = new MediaTracker( gameShell );

		// add your image to the tracker with an arbitrary id
		int id = 0;
		media_tracker.addImage( tempImage, id );
		
		try
		{
			media_tracker.waitForID( id );
		}
		catch( InterruptedException e )
		{
			
		}

		media_tracker = null;
		
		VideoImage newVideoImage = new VideoImage( gameShell );
		newVideoImage.copy( tempImage );
		
		//newVideoImage.setRealDimensions( newVideoImage.getRealWidth(), newVideoImage.getRealHeight() );
		newVideoImage.setDimensions( getWidth(), getHeight() );
		
		videoImageList.add( newVideoImage );
		
		animation.setNumberOfFrames( videoImageList.size() );
	}
	
	public void loadFrame( String fileName )
	{
		VideoImage newVideoImage = new VideoImage( gameShell );
		newVideoImage.load( fileName );
		videoImageList.add( newVideoImage );
	}
	
	public Image getBuffer()
	{
		return( videoImageList.elementAt( getCurrentFrame() ).getBuffer() );
	}
	
	public void loadAnimation( int[] newSequence )
	{
		// Load in data for animation
		animation.load( newSequence );
	}
	
	public int getAnimation()
	{
		return( animation.getCurrentSequence() );
	}
	
	public void setAnimation( int animationIndex )
	{
		// Set the animation index
		animation.setCurrentSequence( animationIndex );
	}
	
	public void setAnimationSpeed( int speed )
	{
		// Set speed
		animation.setSpeed( speed );
	}
	
	public int getAnimationSpeed()
	{
		return( animation.getSpeed() );
	}
	
	
	public int getAnimationCounter()
	{
		return( animation.getCounter() );
	}
	
	
	public void animate()
	{
		// Animate with current animation sequence
		animation.read();
	}
	
	
	public int getCurrentFrame()
	{
		return( animation.getCurrentFrame() );
	}
	
	public void setCurrentFrame( int frameIndex )
	{
		animation.setCurrentFrame( frameIndex );
	}

        public int getFrameIndex()
        {
            return( animation.getFrameIndex() );
        }

        public void setFrameIndex( int frameIndex )
        {
            animation.setFrameIndex( frameIndex );
        }
	
	//public void setColor( Color c )
	public void setColor( float[] c )
	{
		colorWeight[0] = c[0];
		colorWeight[1] = c[1];
		colorWeight[2] = c[2];
		
		//color = null;
		color = new Color( colorWeight[0], colorWeight[1], colorWeight[2] );
	}
	
	public void setColor( int r, int g, int b )
	{
		colorWeight[0] = ( float )r / 255.0f;
		colorWeight[1] = ( float )g / 255.0f;
		colorWeight[2] = ( float )b / 255.0f;
		
		//color = null;
		color = new Color( colorWeight[0], colorWeight[1], colorWeight[2] );
	}
	
	public void setColor( float r, float g, float b )
	{
		colorWeight[0] = r;
		colorWeight[1] = g;
		colorWeight[2] = b;
		
		//color = null;
		color = new Color( colorWeight[0], colorWeight[1], colorWeight[2] );
	}
	
	public float[] getColor()
	{
		return( colorWeight );
	}
	
	public void setAlpha( float a )
	{
		if( a < 0.0f )
		{
			a = 0.0f;
		}
		
		alpha = a;
		colorWeight[3] = alpha;
	}
	
	public float getAlpha()
	{
		return( alpha );
	}

        public float getAngle()
        {
            return ( ( float )Math.toDegrees( angle ) );
        }

        public void setAngle( float angle )
        {
            this.angle = ( float )Math.toRadians( angle );
        }

        public Point getRotationAxis()
        {
            return( rotationAxis );
        }

        public void setRotationAxis( int x, int y )
        {
            this.rotationAxis.x = x;
            this.rotationAxis.y = y;
        }
}